package com.github.gaojh.server.handler;

import cn.hutool.cache.Cache;
import cn.hutool.cache.CacheUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpStatus;
import com.fasterxml.jackson.databind.JavaType;
import com.github.gaojh.mvc.annotation.PathParam;
import com.github.gaojh.mvc.annotation.RequestBody;
import com.github.gaojh.mvc.annotation.RequestMethod;
import com.github.gaojh.mvc.annotation.RequestParam;
import com.github.gaojh.mvc.filter.FilterChain;
import com.github.gaojh.mvc.filter.FilterContext;
import com.github.gaojh.mvc.route.RouteInfo;
import com.github.gaojh.mvc.route.RouterUtil;
import com.github.gaojh.server.exception.FlyingException;
import com.github.gaojh.server.http.*;
import com.github.gaojh.server.utils.JsonTools;
import com.github.gaojh.server.utils.PathMatcher;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import lombok.extern.slf4j.Slf4j;

import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

/**
 * @author gaojh
 * @since 2020/3/23
 */
@Slf4j
public class DispatcherHandler {

    private Cache<String, byte[]> cache = CacheUtil.newLFUCache(1000);

    public HttpContext buildHttpContext(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) {
        HttpContext httpContext = new HttpContext();
        httpContext.setCtx(ctx);
        httpContext.setRequest(fullHttpRequest);
        httpContext.setHttpRequest(new Request(fullHttpRequest, ctx));
        httpContext.setHttpResponse(new Response());
        httpContext.setStaticResourceBool(false);
        httpContext.setInterceptorBool(false);
        httpContext.setFilterChain(new FilterChain(FilterContext.getInterceptor(httpContext.getHttpRequest().getUrl())));
        return httpContext;
    }

    public HttpContext checkStaticResource(HttpContext httpContext) {
        String url = httpContext.getHttpRequest().getUrl();
        if (cache.containsKey(url)) {
            httpContext.setStaticResourceBool(true);
            httpContext.setStaticResource(new StaticResource().setRequestUri(url).setBytes(cache.get(url)).setFileName(StrUtil.subAfter(url, "/", true)).setSuffix(StrUtil.subAfter(url, ".", true)));
            return httpContext;
        }

        InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("/public" + url);
        if (is == null) {
            is = Thread.currentThread().getContextClassLoader().getClass().getResourceAsStream("/static" + url);
        }

        if (is != null) {
            byte[] bytes = IoUtil.readBytes(is);
            cache.put(url, bytes);
            httpContext.setStaticResourceBool(true);
            httpContext.setStaticResource(new StaticResource().setRequestUri(url).setBytes(cache.get(url)));
        } else {
            httpContext.setStaticResourceBool(false);
        }

        return httpContext;
    }

    public HttpContext handleInterceptorBefore(HttpContext httpContext){
        FilterChain filterChain = httpContext.getFilterChain();
        try {
            filterChain.doBefore(httpContext);
        } catch (Exception e) {
            e.printStackTrace();
            throw new FlyingException(HttpStatus.HTTP_INTERNAL_ERROR,"前置拦截器执行异常");
        }
        return httpContext;
    }

    public HttpContext handleInterceptorAfter(HttpContext httpContext){
        FilterChain filterChain = httpContext.getFilterChain();
        try {
            filterChain.doAfter(httpContext);
        } catch (Exception e) {
            e.printStackTrace();
            throw new FlyingException(HttpStatus.HTTP_INTERNAL_ERROR,"后置拦截器执行异常");
        }
        return httpContext;
    }

    public HttpContext invokeRoute(HttpContext httpContext) {
        if (httpContext.isStaticResourceBool()) {
            return httpContext;
        }

        if(httpContext.isInterceptorBool()){
            return httpContext;
        }

        RouteInfo routeInfo = RouterUtil.getRouter(httpContext.getHttpRequest().getUrl());
        if (routeInfo == null) {
            throw new FlyingException(HttpStatus.HTTP_BAD_REQUEST, "未配置对应的路由：" + httpContext.getHttpRequest().getUrl());
        }
        HttpRequest httpRequest = httpContext.getHttpRequest();

        if (Arrays.stream(routeInfo.getRequestMethod()).noneMatch(requestMethod -> requestMethod.equals(RequestMethod.valueOf(httpRequest.getMethod().name())))) {
            throw new FlyingException(HttpStatus.HTTP_FORBIDDEN, "不支持该请求方式：" + httpRequest.getMethod().name());
        }

        HttpResponse httpResponse = httpContext.getHttpResponse();
        Method method = routeInfo.getMethod();
        Parameter[] params = method.getParameters();
        Type[] types = method.getGenericParameterTypes();
        for (int i = 0; i < params.length; i++) {
            Parameter parameter = params[i];
            if (parameter.isAnnotationPresent(RequestBody.class)) {
                Type type = types[i];
                if (httpRequest.getBody() == null) {
                    routeInfo.getParams()[i] = null;
                } else {
                    JavaType javaType = JsonTools.DEFAULT.getMapper().getTypeFactory().constructType(type);
                    routeInfo.getParams()[i] = JsonTools.DEFAULT.fromJson(httpRequest.getBody(), javaType);
                }
            } else if (parameter.isAnnotationPresent(RequestParam.class)) {
                RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
                String name = StrUtil.isBlank(requestParam.value()) ? routeInfo.getParamNames()[i] : requestParam.value();
                String value = httpContext.getHttpRequest().getParameters().get(name);
                if (value == null) {
                    log.info("缺少参数名为{}的值！设为null！", name);
                } else {
                    routeInfo.getParams()[i] = Convert.convert(parameter.getType(), value);
                }
            } else if (parameter.isAnnotationPresent(PathParam.class)) {
                Map<String, String> map = PathMatcher.path.extractUriTemplateVariables(routeInfo.getRequestPath(), httpRequest.getUrl());
                routeInfo.getParams()[i] = Convert.convert(parameter.getType(), map.get(routeInfo.getParamNames()[i]));
            } else if (parameter.getType().isAssignableFrom(HttpRequest.class)) {
                routeInfo.getParams()[i] = httpRequest;
            } else if (parameter.getType().isAssignableFrom(HttpResponse.class)) {
                routeInfo.getParams()[i] = httpResponse;
            } else {
                routeInfo.getParams()[i] = null;
            }
        }

        Object result = null;
        if (!method.isAccessible()) {
            method.setAccessible(true);
        }
        try {
            result = method.invoke(routeInfo.getControllerObject(), routeInfo.getParams());
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }

        if (result == null) {
            httpContext.setResult("");
        } else if ("java.lang.String".equals(result.getClass().getName())) {
            httpContext.setResult(result.toString());
        } else {
            //非字符串类型的将对象转换Json字符串
            httpContext.setResult(JsonTools.DEFAULT.toJson(result));
            httpContext.getHttpResponse().addHeader("content-type", "application/json;charset=UTF-8");
        }
        return httpContext;
    }

    public FullHttpResponse buildResponse(HttpContext httpContext) {
        try {
            FullHttpResponse response;
            if (httpContext.isStaticResourceBool()) {
                response = BuildResponse.buildStaticResourceResponse(httpContext);
            } else if (httpContext.getHttpResponse().isDownload()) {
                response = BuildResponse.buildDownloadResponse(httpContext);
            } else if (httpContext.getHttpResponse().getJson() != null) {
                response = BuildResponse.buildJsonResponse(httpContext);
            } else {
                response = BuildResponse.buildRouteInvokeResponse(httpContext);
            }
            return BuildResponse.postSetHeader(response, httpContext);
        } catch (Exception e) {
            e.printStackTrace();
            String message = ExceptionUtil.getMessage(e);
            log.error(message);
            throw new FlyingException(HttpStatus.HTTP_INTERNAL_ERROR, "构建Response对象异常" + message);
        }
    }

    public void writeResponse(ChannelHandlerContext ctx, CompletableFuture<HttpContext> future, FullHttpResponse msg) {
        ctx.writeAndFlush(msg);
        future.complete(null);
    }
}
